/**
 * Copyright 2011 Honeywell Federal Manufacturing & Technologies, LLC. This material was produced under U.S. Government
 * contract DE-ACO4-01AL66850, Honeywell Federal Manufacturing & Technologies, LLC, (FM&T) with the U. S. Department of
 * Energy for the operation of the Kansas City Plant . The U.S. Government has rights to use, reproduce, and distribute
 * this software.  NEITHER THE GOVERNMENT NOR Honeywell Federal Manufacturing & Technologies, LLC, MAKES ANY WARRANTY,
 * EXPRESS OR IMPLIED, OR ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE.  If software is modified to produce
 * derivative works, such modified software should be clearly marked, so as not to confuse it with the version available
 * from FM&T.
 *
 * Additionally, this program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License Version 3 published by the Free Software Foundation. Accordingly, this program is distributed in
 * the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this library.
 * If not, see http://www.gnu.org/licenses/.
 */
package org.braintrain.tapestry.services;

import com.lowagie.text.Document;
import com.lowagie.text.PageSize;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.DefaultFontMapper;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfWriter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.PageRedirectException;
import org.apache.tapestry.util.ContentType;
import org.apache.tapestry.web.WebResponse;
import org.braintrain.domain.AnswerLog;
import org.braintrain.domain.Module;
import org.braintrain.domain.Question;
import org.braintrain.domain.TestData;
import org.braintrain.services.search.navigation.TestCriteriaFactory;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.DefaultCategoryDataset;

import javax.activation.MimetypesFileTypeMap;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The Tapestry Service for building and making the Missed Question Graph available  
 * 
 * @author Robert H. Engelhardt
 * 
 */
@Deprecated
public class MissedQuestionGraphReportService extends BrainTrainEngineServiceAdapter {

	public static final String SERVICE_NAME = "missedQuestionGraphReportService";
	public static final String FILE_NAME = "MissedQuestionReport.pdf";
	public static final String MODULE_ID = "moduleId";

	private MimetypesFileTypeMap mimeTypes = new MimetypesFileTypeMap();

	private static final Log log = LogFactory.getLog(MissedQuestionGraphReportService.class); // define log

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.tapestry.engine.IEngineService#getName()
	 */
	public String getName() {
		return SERVICE_NAME;
	}

	/*
	 * (non-Javadoc)
	 * @see org.apache.tapestry.engine.IEngineService#service(org.apache.tapestry.IRequestCycle)
	 */
	public void service(IRequestCycle cycle) throws IOException {

		if (!getBraintrainVisit().getAuthenticatedUser().hasBasicAccess()) {
			throw new PageRedirectException("NotAuthorized");
		}

		log.info(getBraintrainVisit().getAuthenticatedUser() + " retrieved file via " + SERVICE_NAME); // log event

		Long moduleId = unsqueezeParameter(cycle, Long.class, MODULE_ID);
		Module module = getHibernateSearchService().getById(Module.class, moduleId);
		JFreeChart chart = generateChart(module);   // build chart

		WebResponse response = cycle.getInfrastructure().getResponse();
		response.setHeader("Content-disposition", "attachment; filename=" + FILE_NAME);
		response.setHeader("Cache-Control", "max-age=0");
		OutputStream out = response.getOutputStream(new ContentType(mimeTypes.getContentType(FILE_NAME)));
		renderJFreeChartToPDF(chart, out);    // convert chart to PDF and send to outputstream
		out.flush();

	}
	
	/**
	 * Create a JFreechart based on the missed questions of a module
	 * @param module the module to chart the missed questions of
	 * @return the created JFreechart object
	 */
	private JFreeChart generateChart(final Module module) {
		 
		//XXX this is a functionality kluge that has put in place until the domain object graph has been changed and formalized.
		
		Map<TestQuestionKey, Integer> missedQuestions = new HashMap<TestQuestionKey, Integer>();	// a map of questions to the number of times it has been missed
		
		// accumulate all the questions associated with a module to do this get all tests associated with the given module
		// It is possible for a question to never be asked, thus having no logs. It must still be shown on the graph with 0 incorrect answers, hence the 0 bootstrap.
		List<TestData> tests = getHibernateSearchService().find(new TestCriteriaFactory(module));	// get all the test questions
		for(TestData test : tests) {
			for(Question question : test.getQuestions()) {	// add each question to the map, missed count starts at zero
				missedQuestions.put(new TestQuestionKey(test, question), 0);	// boot strap each question as having no incorrect responses
			}
		}

		// Looks like we fail somewhera around here when using production data... Toomany answer logs?
		List<AnswerLog> answerLogs = getHibernateSearchService().getAll(AnswerLog.class);	// get all the answer logs in the system (ick)
		// iterate through answer logs in order to relate to tests/questions 
		for(AnswerLog answerLog: answerLogs) {
			if(!answerLog.isCorrect()) {	// only deal with incorrect answers 
				Question question = answerLog.getAnswer().getQuestions();
				TestData test = question.getTest();
				TestQuestionKey key = new TestQuestionKey(test, question);
				if(missedQuestions.containsKey(key)) {	// only deal with questions that are apart of the logged
					Integer count = missedQuestions.get(key);
					missedQuestions.put(new TestQuestionKey(test, question), count+1);	// increment the missed count of given question
				}
			}
		}
		
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		// for all the missed question count
		for(Map.Entry<TestQuestionKey, Integer> missedQuestion : missedQuestions.entrySet()) {
			String question = missedQuestion.getKey().getQuestion();
			Integer missed = missedQuestion.getValue();
			dataset.setValue(missed, "Questions Missed", question);
		}
		
		JFreeChart chart = ChartFactory.createBarChart3D("Question Missed for Module " + module.getNullSafeName().getValue(), "Question", "Number Missed", dataset, PlotOrientation.HORIZONTAL, false, false, false);
		CategoryPlot plot = chart.getCategoryPlot();
		plot.setBackgroundPaint(Color.white);
		plot.setDomainGridlinePaint(Color.lightGray);
		plot.setRangeGridlinePaint(Color.lightGray);
		plot.getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
		plot.getDomainAxis().setMaximumCategoryLabelLines(5);
		plot.getRenderer().setSeriesPaint(0, Color.cyan);
		return chart;

	}

	
//	/**
//	 * Create a JFreechart based on the missed questions of a module
//	 * @param module the module to chart the missed questions of
//	 * @return the created JFreechart object
//	 */
//	private JFreeChart generateChart(final Module module) {
//		
//		Map<Questions, Integer> missedQuestions = new HashMap<Questions, Integer>();	// a map of questions to the number of times it has been missed
//
//		// Get all TestData associated with the given module
//		// It is possible for a question to never be asked, thus having no logs. It must still be shown on the graph with 0 incorrect answers, hence the 0 bootstrap
//		List<TestData> tests = getHibernateSearchService().find(new TestCriteriaFactory(module));	// get all the test questions
//		for(TestData test : tests) {
//			for(Questions question : test.getQuestions()) {	// add each question to the map, missed count starts at zero
//				missedQuestions.put(question, 0);
//			}			
//		}
//		
//		CriteriaFactory<AnswerLog> answerLogCriteriaFactory = new CriteriaFactory<AnswerLog>() {
//			public Criteria getCriteria(Session session) {
//				Criteria criteria = session.createCriteria(AnswerLog.class);
//				criteria.add(Property.forName("module").eq(module));	// get answerLogs for given module
//				criteria.add(Property.forName("correct").eq(false));	// only collect logs that were answered incorrectly
//				criteria.createCriteria("answer").createCriteria("questions").createCriteria("test").add(Property.forName("logTestResults").eq(true)); // only care about questions that are currently logged
//				return criteria;
//			}
//		};
//		
//		List<AnswerLog> answerLogs = getHibernateSearchService().find(answerLogCriteriaFactory);	
//		for(AnswerLog answerLog : answerLogs ) {
//			Questions question = answerLog.getAnswer().getQuestions();
//			Integer count = missedQuestions.get(question);
//			missedQuestions.put(question, count+1);	// increment the missed count of given question
//		}
//		
//		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
//		// for all the missed question count
//		for(Map.Entry<Questions, Integer> missedQuestion : missedQuestions.entrySet()) {
//			Questions question = missedQuestion.getKey();
//			Integer missed = missedQuestion.getValue();
//			dataset.setValue(missed, "Questions Missed", question.getQuestion());
//		}
//		
//		JFreeChart chart = ChartFactory.createBarChart3D("Question Missed for Module " + module.getName().getValue(), "Question", "Number Missed", dataset, PlotOrientation.HORIZONTAL, false, false, false);
//		CategoryPlot plot = chart.getCategoryPlot();
//		plot.setBackgroundPaint(Color.white);
//		plot.setDomainGridlinePaint(Color.lightGray);
//		plot.setRangeGridlinePaint(Color.lightGray);
//		plot.getRangeAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
//		plot.getDomainAxis().setMaximumCategoryLabelLines(5);
//		plot.getRenderer().setSeriesPaint(0, Color.cyan);
//		return chart;
//		
//	}
	
	/**
	 * Convert a JFreeChart to a PDF and pipe to the output stream
	 * @param chart the chart to convert
	 * @param out the output stream to channel the PDF to
	 */
	private void renderJFreeChartToPDF(final JFreeChart chart, OutputStream out) {

		Rectangle page = PageSize.A4;
		float width = page.getWidth();
		float height = page.getHeight();
		
		Document document = new Document(page);
		
		try {
			
			PdfWriter writer = PdfWriter.getInstance(document, out);
			
			document.open();
			
			PdfContentByte contentByte = writer.getDirectContent();
			PdfTemplate template = contentByte.createAppearance(width, height);
			Graphics2D graphics = template.createGraphics(width, height, new DefaultFontMapper());
			Rectangle2D rectangle = new Rectangle2D.Double(0, 0, width, height);

			chart.draw(graphics, rectangle);
			graphics.dispose();
			contentByte.addTemplate(template, 0, 0);
			
		} catch (Exception e){
			throw new RuntimeException("Could not convert chart to PDF.", e);
		}
		
		document.close();
		
	}

	/**
	 * A private tuple class used as a Key to a map
	 */
	private static class TestQuestionKey {
		private String test;
		private String question;
		
		/**
		 * Construct a new TestQuestionKey
		 * @param test the test to get the name from
		 * @param question the question to get the stirng question from
		 */
		public TestQuestionKey(TestData test, Question question) {
			super();
			this.test = test.getName();
			this.question = question.getQuestion();
		}
		
		/**
		 * @return the test
		 */
		public String getTest() {
			return test;
		}
		/**
		 * @param test the test to set
		 */
		public void setTest(String test) {
			this.test = test;
		}
		/**
		 * @return the question
		 */
		public String getQuestion() {
			return question;
		}
		/**
		 * @param question the question to set
		 */
		public void setQuestion(String question) {
			this.question = question;
		}
		/* (non-Javadoc)
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result
					+ ((question == null) ? 0 : question.hashCode());
			result = prime * result + ((test == null) ? 0 : test.hashCode());
			return result;
		}
		/* (non-Javadoc)
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			TestQuestionKey other = (TestQuestionKey) obj;
			if (question == null) {
				if (other.question != null)
					return false;
			} else if (!question.equals(other.question))
				return false;
			if (test == null) {
				if (other.test != null)
					return false;
			} else if (!test.equals(other.test))
				return false;
			return true;
		}
	}
	
}
